package org.dreamwork.modbus.tcp;

import org.dreamwork.modbus.ModbusException;
import org.dreamwork.modbus.protocol.PDU;
import org.dreamwork.modbus.tcp.protocol.TcpADU;
import org.dreamwork.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.dreamwork.modbus.Predefined.Exception.*;
import static org.dreamwork.modbus.Predefined.Function.*;

public class Worker implements Runnable {
    private final Socket socket;
    private final IServerHandler listener;
    private final AtomicBoolean running = new AtomicBoolean (true);
    private final List<Worker> container;
    private final Logger logger = LoggerFactory.getLogger (Worker.class);

    public Worker (Socket socket, IServerHandler listener, List<Worker> container) {
        this.socket = socket;
        this.listener = listener;
        this.container = container;

        if (socket == null || listener == null) {
            throw new NullPointerException ();
        }
        container.add (this);
    }

    @Override
    public void run () {
        try {
            InputStream   in = socket.getInputStream ();
            OutputStream out = socket.getOutputStream ();
            byte[] buff = new byte[256];
            while (running.get ()) {
                int len = in.read (buff);
                if (len == -1) {
                    break;
                }
                if (logger.isTraceEnabled ()) {
                    logger.trace ("************************************************");
                    logger.trace ("received raw data: \r\n{}", StringUtil.format (buff, 0, len));
                    logger.trace ("************************************************");
                }
                TcpADU adu = Helper.parse (buff);
                if (logger.isTraceEnabled ()) {
                    logger.trace ("pdu: {");
                    logger.trace ("\tfunction : {}", adu.pdu.function);
                    logger.trace ("\taddress  : {}", adu.pdu.address);
                    logger.trace ("\tcount    : {}", adu.pdu.count);
                    logger.trace ("\tsub code : {}", adu.pdu.subCode);
                    logger.trace ("}");
                }
                PDU pdu;
                try {
                    pdu = perform (listener, adu.pdu);
                } catch (ModbusException me) {
                    pdu = new PDU ();
                    pdu.function = adu.pdu.function | 0x80; // 有错误发送，MSB置1
                    pdu.error = me.code;
                }

                TcpADU response = new TcpADU ();
                response.header = adu.header;
                response.pdu = pdu;
                if (logger.isTraceEnabled ()) {
                    byte[] temp = response.toByteArray ();
                    logger.trace ("============ server respond: \r\n{}", StringUtil.format (temp));
                    System.out.println ("==============================");
                    out.write (temp);
                } else {
                    out.write (response.toByteArray ());
                }
                out.flush ();
            }
            if (logger.isTraceEnabled ()) {
                logger.trace ("job done");
            }
        } catch (Exception ex) {
            logger.warn (ex.getMessage (), ex);
        } finally {
            container.remove (this);
            if (socket != null) {
                try {
                    socket.close ();
                } catch (IOException ex) {
                    ex.printStackTrace ();
                }
            }
        }
    }

    public void abort () {
        if (logger.isTraceEnabled ()) {
            logger.trace ("aborting worker...");
        }
        running.set (false);
    }

    private PDU perform (IServerHandler handler, PDU pdu) throws ModbusException {
        boolean[] flags;
        int[] values;

        PDU result = new PDU ();
        result.function= pdu.function;

        switch (pdu.function) {
            case Read_Coils:
                flags = handler.onReadCoils (pdu.address, pdu.count);
                // 线圈读操作的代码返回值 boolean[]，但在modbus中使用一个位来表示一个线圈状态，需要做数据转化
                result.data = Helper.bool2byte (flags);
                return result;
            case Read_Discrete_Inputs:
                flags = handler.onReadDiscreteInputs (pdu.address, pdu.count);
                // 线圈读操作的代码返回值 boolean[]，但在modbus中使用一个位来表示一个线圈状态，需要做数据转化
                result.data = Helper.bool2byte (flags);
                return result;
            case Read_Holding_Registers:
                values = handler.onReadHoldingRegisters (pdu.address, pdu.count);
                // modbus 中每个寄存器值占2个字节
                result.data = Helper.int2byte (values);
                return result;
            case Read_Input_Registers:
                values = handler.onReadInputRegisters (pdu.address, pdu.count);
                // modbus 中每个寄存器值占2个字节
                result.data = Helper.int2byte (values);
                return result;
            case Write_Single_Coil:
                if (pdu.value != 0 && pdu.value != 0xff00) {
                    throw new ModbusException (ILLEGAL_DATA_VALUE, pdu.address);
                }
                handler.onWriteSingleCoil (pdu.address, pdu.value == 0xff00);
                // 单写操作的应答：和请求一样
                result.value = pdu.value;
                return result;
            case Write_Single_Register:
                handler.onWriteSingleRegister (pdu.address, pdu.value);
                // 单写操作的应答：和请求一样
                result.value = pdu.value;
                return result;
            case Write_Multiple_Coils:
                writeMultipleCoils (handler, pdu);
                // 复写操作的应答：从请求pdu中去除数据部分
                result.address = pdu.address;
                result.count   = pdu.count;
                return result;
            case Write_Multiple_Registers:
                writeMultipleRegisters (handler, pdu);
                // 复写操作的应答：从请求pdu中去除数据部分
                result.address = pdu.address;
                result.count   = pdu.count;
                return result;
            default:
                result = handler.handleRawData (pdu);
                return result;
        }
    }

    private void writeMultipleCoils (IServerHandler handler, PDU pdu) throws ModbusException {
        handler.onWriteMultipleCoils (pdu.address, Helper.byte2bool (pdu.data, pdu.count));
    }

    private void writeMultipleRegisters (IServerHandler handler, PDU pdu) throws ModbusException {
        int size = pdu.count;
        int[] data = new int[size];
        int pos = 0;
        for (int i = 0; i < size; i ++) {
            data[i] = ((pdu.data[pos ++] & 0xff) << 8) | (pdu.data[pos ++] & 0xff);
        }
        handler.onWriteMultipleRegisters (pdu.address, data);
    }
}